﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EFMagic.Schema;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Reflection;

namespace EFMagic
{
    public class EFMCodeFirstGenerator
    {
        EFMSchema schema;
        
        string path = "";

        public EFMCodeFirstGenerator(EFMSchema schema, string entityFrameworkDllPath)
        {
            this.schema = schema;
            path = entityFrameworkDllPath;
        }

        public string GenerateSchema()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("using System;");
            sb.AppendLine("using System.Collections;"); 
            sb.AppendLine("using System.Collections.ObjectModel;"); 
            sb.AppendLine("using System.ComponentModel;");
            sb.AppendLine("using System.Collections.Generic;");
            sb.AppendLine("using System.Data.Entity;");
            sb.AppendLine("using System.ComponentModel.DataAnnotations;");
            sb.AppendLine("using System.Linq;");
            sb.AppendFormat("namespace {0} {{\n", schema.Name);

            sb.AppendLine(@"public class ObservableListSource<T> : ObservableCollection<T>, IListSource where T : class{private IBindingList _bindingList;bool IListSource.ContainsListCollection { get { return false; } } IList IListSource.GetList(){ return _bindingList ?? (_bindingList = this.ToBindingList());}}");
            sb.AppendLine(@" public static class EFMPOCOExtensions {
                             public static object GetPropertyValue<T>(this T Source, string Name) { try{return typeof(T).GetProperty(Name).GetValue(Source, null);}catch(Exception ex){return null;} }
                             public static IEnumerable<string> GetProperties<T>(this T Source){return (from x in typeof(T).GetProperties() select x.Name);} }");
            sb.AppendLine();
            var tables = from x in schema.Tables
                         select x;
            
            foreach (var table in tables)
            {
                sb.AppendFormat("\t[Table(\"{0}\")]\n", table.Value.DBName);
                sb.AppendFormat("\tpublic class {0} {{\n", table.Value.Name);
                foreach (var column in table.Value.Columns)
                {
                    sb.AppendFormat("\t\t[Column(\"{0}\")]\n",column.Value.DBName);
                    sb.AppendFormat("\t\tpublic {0} {1} {{get;set;}}\n", column.Value.DataType, column.Value.Name);
                }
                foreach (var rel in from x in table.Value.Constraints where x.Value is EFMForeignKey select x)
                {
                    sb.AppendFormat("\t\tpublic virtual {0} {0} {{get;set;}}\n", schema.Tables[(rel.Value as EFMForeignKey).RTableId].Name);
                }
                var rels = from x in schema.Tables
                           where (from y in x.Value.Constraints
                                  where y.Value is EFMForeignKey && (y.Value as EFMForeignKey).RTableId == table.Value.Id
                                  select y).Count() > 0
                           select x;
                foreach (var rel in rels)
                {
                    sb.AppendFormat("\t\tpublic virtual ObservableListSource<{0}> {0}s {{get;set;}}\n", rel.Value.Name);
                }
                
                sb.AppendFormat("\t\tpublic {0}() {{\n", table.Value.Name);
                foreach (var rel in rels)
                {
                    sb.AppendFormat("\t\tthis.{0}s = new ObservableListSource<{0}>();\n", rel.Value.Name);
                }
                sb.AppendLine("\t\t}");
                sb.AppendLine("\t}");
                
            }

            sb.AppendFormat("\tpublic class {0}Context : DbContext {{\n", schema.Name);
            sb.AppendFormat("\t\tpublic {0}Context() : base() {{}}\n\t\tpublic {0}Context(string connectionString) : base(connectionString) {{}}\n", schema.Name);
            foreach (var table in tables)
            {
                sb.AppendFormat("\t\tpublic DbSet<{0}> {0}s {{get;set;}}\n",table.Value.Name);
            }
            sb.AppendLine("\t}");
            sb.AppendLine("}");
            
            return sb.ToString();
        }

        public Assembly Compile()
        {
            var code = GenerateSchema();
            var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } });
            var parameters = new CompilerParameters(new[] { "System.dll", "System.Core.dll", path + "EntityFramework.dll", "System.Data.Entity.dll" }, "", true);
            parameters.GenerateExecutable = false;
            CompilerResults results = csc.CompileAssemblyFromSource(parameters, code);
            return results.CompiledAssembly;
        }

        public void CompileToFile(string filename)
        {
            var code = GenerateSchema();
            var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } });
            var parameters = new CompilerParameters(new[] { "System.dll", "System.Core.dll", path + "EntityFramework.dll", "System.Data.Entity.dll" }, filename, true);
            parameters.GenerateExecutable = false;
            CompilerResults results = csc.CompileAssemblyFromSource(parameters, code);
        }

    }
}